#ifndef GTL_STRINGS_FORMAT_HEADER_GUARD
#define GTL_STRINGS_FORMAT_HEADER_GUARD

#if GTL_COMPILER_VISUAL_STUDIO
#pragma warning(disable:4996)
#endif


class FormatArgument;

template <typename FORMAT_TYPE>
struct FormatStringPtr
{
  const FORMAT_TYPE& ptr;
  size_t position;

  FormatStringPtr(const FORMAT_TYPE& create);
  template <typename SINK_TYPE>
  void AppendTo(SINK_TYPE& sink);

  bool IsString() const;
  bool IsArgument() const;
  FormatArgument GetArgument(size_t given_arguments);

  int Next();
};

template <>
struct FormatStringPtr<const char*>
{
  const char* ptr;
  size_t position;

  FormatStringPtr(const char* create, size_t pos = 0) : ptr(create), position(pos) { }

  template <typename SINK_TYPE>
  void AppendTo(SINK_TYPE& sink);

  bool IsString() const { return ptr[position] != '{' && ptr[position] != 0; }
  bool IsArgument() const { return ptr[position] == '{'; }
  FormatArgument GetArgument(size_t given_arguments);
  //FormatArgument IsArgument(size_t given_arguments) const;

  bool HasEnded() const
  {
    return ptr[position] == 0;
  }

  int Next()
  {
    if (IsString()) /// move to the next '{'
    {
      while (ptr[position] != 0 && ptr[position] != '{')
        position++;
      return ptr[position];
    } else if (IsArgument()) /// move to the next '}'
    {
      while (ptr[position] != 0 && ptr[position] != '}')
        position++;
      if (ptr[position] == 0) return 0;
      else return ptr[++position];
    } else
    {
      return 0;
    }

    return 0;
  }
};

#ifdef GTL_COMPILER_ARM
template <>
#endif
template <>
inline void FormatStringPtr<const char*>::AppendTo<eastl::string>(eastl::string& sink)
{
  const char* start = ptr+position;
  const char* nextArg = strchr(start, '{');
  if (!nextArg)
    sink += start;
  else
  {
    sink.append(start, nextArg-start);
    position += nextArg-start;
  }
}

#ifdef GTL_COMPILER_ARM
template <>
#endif
template <>
inline void FormatStringPtr<const char*>::AppendTo<std::ostringstream>(std::ostringstream& sink)
{
  const char* start = ptr+position;
  const char* nextArg = strchr(start, '{');
  if (!nextArg)
    sink << start;
  else
  {
		sink.write(start, (std::streamsize)(nextArg-start));
    position += nextArg-start;
  }
}

#ifdef GTL_COMPILER_ARM
template <>
#endif
template <>
inline void FormatStringPtr<const char*>::AppendTo<char*>(char*& sink)
{
  const char* start = ptr+position;
  const char* nextArg = strchr(start, '{');
  if (!nextArg)
    strcat(sink, start);
  else
  {
    strncat(sink, start, nextArg-start);
    position += nextArg-start;
  }
}

struct SinkDrainBase
{
  virtual void DrainCharacter(int character) = 0;
  virtual void DrainSpecial(int, void*) {}
};

template <typename T>
void ToString(SinkDrainBase* drain, T object, const FormatArgument& argument);

class FormatArgument
{
  template <typename T>
  friend eastl::string ToString(T object, const FormatArgument& argument);
public:
  FormatArgument(bool is, int num = -1) : Number(num), mIs(is)
  {
    if (!is) Number = -1;
  }

  operator bool() const { return mIs; }

  struct _IntParams
  {
    int Base;

    _IntParams() : Base(10) { }
  } IntParams;

  int Number;
private:
  bool mIs;
};

inline bool _isdigit(int d) { return d>='0'&&d<='9'; }

inline FormatArgument FormatStringPtr<const char*>::GetArgument(size_t given_arguments)
{
  if (ptr[position] == '{')
  {
    unsigned char num = ptr[position+1];
    if (num == 0) return FormatArgument(false);
    if (!_isdigit(num)) return FormatArgument(false);

    num = num-'0';
    if (num >= given_arguments) return FormatArgument(false);

    position += 3;
    return FormatArgument(true, num);
  }

  return FormatArgument(false);
}

template <>
inline void ToString<const char*>(SinkDrainBase* drain, const char* object, const FormatArgument& argument)
{
  for (size_t i=0; object[i]; i++)
    drain->DrainCharacter(object[i]);
}

template <>
inline void ToString<char*>(SinkDrainBase* drain, char* object, const FormatArgument& argument)
{
  for (size_t i=0; object[i]; i++)
    drain->DrainCharacter(object[i]);
}

template <>
inline void ToString<const eastl::string&>(SinkDrainBase* drain, const eastl::string& object, const FormatArgument& argument)
{
	for (size_t i=0; object[i]; i++) /// TODO: FIXME: Wrong! string nie musi miec na koncu \0!!
    drain->DrainCharacter(object[i]);
}

template <>
inline void ToString<eastl::string>(SinkDrainBase* drain, eastl::string object, const FormatArgument& argument)
{
  for (size_t i=0; object[i]; i++) /// TODO: FIXME: Wrong! string nie musi miec na koncu \0!!
    drain->DrainCharacter(object[i]);
}

template <>
inline void ToString<int>(SinkDrainBase* drain, int object, const FormatArgument& argument)
{
  char temp[32];
  IntegerToA8String(temp, object, argument.IntParams.Base);
  for (size_t i=0; temp[i]; i++)
    drain->DrainCharacter(temp[i]);
}

template <>
inline void ToString<unsigned int>(SinkDrainBase* drain, unsigned int object, const FormatArgument& argument)
{
  char temp[32];
  IntegerToA8String(temp, object, argument.IntParams.Base);
  for (size_t i=0; temp[i]; i++)
    drain->DrainCharacter(temp[i]);
}

template <>
inline void ToString<long>(SinkDrainBase* drain, long object, const FormatArgument& argument)
{
  char temp[32];
  IntegerToA8String(temp, object, argument.IntParams.Base);
  for (size_t i=0; temp[i]; i++)
    drain->DrainCharacter(temp[i]);
}

template <>
inline void ToString<unsigned long>(SinkDrainBase* drain, unsigned long object, const FormatArgument& argument)
{
  char temp[32];
  IntegerToA8String(temp, object, argument.IntParams.Base);
  for (size_t i=0; temp[i]; i++)
    drain->DrainCharacter(temp[i]);
}

template <>
inline void ToString<uint64_t>(SinkDrainBase* drain, uint64_t object, const FormatArgument& argument)
{
  char temp[32];
  IntegerToA8String(temp, object, argument.IntParams.Base);
  for (size_t i=0; temp[i]; i++)
    drain->DrainCharacter(temp[i]);
}

template <>
inline void ToString<int64_t>(SinkDrainBase* drain, int64_t object, const FormatArgument& argument)
{
  char temp[32];
  IntegerToA8String(temp, object, argument.IntParams.Base);
  for (size_t i=0; temp[i]; i++)
    drain->DrainCharacter(temp[i]);
}

#if !(ULONG_MAX == UINT32_MAX)
template <>
inline void ToString<uint32_t>(SinkDrainBase* drain, uint32_t object, const FormatArgument& argument)
{
  char temp[32];
  IntegerToA8String(temp, object, argument.IntParams.Base);
  for (size_t i=0; temp[i]; i++)
    drain->DrainCharacter(temp[i]);
}

template <>
inline void ToString<int32_t>(SinkDrainBase* drain, int32_t object, const FormatArgument& argument)
{
  char temp[32];
  IntegerToA8String(temp, object, argument.IntParams.Base);
  for (size_t i=0; temp[i]; i++)
    drain->DrainCharacter(temp[i]);
}
#endif

template <>
inline void ToString<float>(SinkDrainBase* drain, float object, const FormatArgument& argument)
{
  char temp[32];
  sprintf(temp,"%f", object);
  for (size_t i=0; temp[i]; i++)
    drain->DrainCharacter(temp[i]);
}

template <>
inline void ToString<bool>(SinkDrainBase* drain, bool object, const FormatArgument& argument)
{
  char* temp = object?"true":"false";
  for (size_t i=0; temp[i]; i++)
    drain->DrainCharacter(temp[i]);
}

template <typename SINK_TYPE>
struct SinkDrain : public SinkDrainBase
{
  SINK_TYPE& sinkObj;

  SinkDrain(SINK_TYPE& sink) : sinkObj(sink) { }

  virtual void DrainCharacter(int character);
  void Drain(const char* str);
  void Drain(const eastl::string& str);
  template <typename FORMAT_TYPE>
  void Drain(const FormatStringPtr<FORMAT_TYPE>& str);
  void Drain(const FormatArgument& arg);
};

template <>
struct SinkDrain<eastl::string> : public SinkDrainBase
{
  eastl::string& sinkObj;

  SinkDrain(eastl::string& sink) : sinkObj(sink) { }

  virtual void DrainCharacter(int character)
  {
    sinkObj += (char)character;
  }
  void Drain(const char* str) { sinkObj += str; }
  void Drain(const eastl::string& str) { sinkObj += str; }

  template <typename FORMAT_TYPE>
  void Drain(const FormatStringPtr<FORMAT_TYPE>& str);
private:
	SinkDrain<eastl::string>& operator=(const SinkDrain<eastl::string>&) { }
};

template <typename FORMAT_TYPE>
inline void SinkDrain<eastl::string>::Drain(const FormatStringPtr<FORMAT_TYPE>& str)
{
  eastl::string woo;
  str.AppendTo<eastl::string>(sinkObj);
}

template <>
struct SinkDrain<char*> : public SinkDrainBase
{
  char* sinkObj;

  SinkDrain(char* sink) : sinkObj(sink)
  {
    /// works as sprintf
    *sinkObj = 0;
  }

  virtual void DrainCharacter(int character)
  {
    //sinkObj += character;
    char c = (char)character;
    strncat(sinkObj, &c, 1);
  }
  void Drain(const char* str) { strcat(sinkObj, str); }
  void Drain(const eastl::string& str) { strcat(sinkObj, str.c_str()); }
  template <typename FORMAT_TYPE>
  void Drain(const FormatStringPtr<FORMAT_TYPE>& str)
  {
    str.AppendTo<char*>(sinkObj);
  }
};

/// this could actually be safe!
template <size_t SIZE>
struct SinkDrain<char[SIZE]> : public SinkDrainBase
{
  char* sinkObj;

#ifdef GTL_DEBUG
  size_t charsLeft;
#endif

  SinkDrain(char* sink) : sinkObj(sink)
  {
    /// works as sprintf
    *sinkObj = 0;
#ifdef GTL_DEBUG
    charsLeft = SIZE;
#endif
  }

  virtual void DrainCharacter(int character)
  {
#ifdef GTL_DEBUG
    if (!charsLeft) return;
#endif
    char c = character;
    strncat(sinkObj, &c, 1);
#ifdef GTL_DEBUG
    charsLeft--;
#endif
  }
  void Drain(const char* str)
  {
    size_t len = strlen(str);
#ifdef GTL_DEBUG
    if (len > charsLeft) len = charsLeft;
#endif
    strncat(sinkObj, str, len);
#ifdef GTL_DEBUG
    charsLeft -= len;
#endif
  }
  void Drain(const eastl::string& str) { Drain(str.c_str()); }
  template <typename FORMAT_TYPE>
  void Drain(FormatStringPtr<FORMAT_TYPE>& str)
  {
    /// TODO: AppendTo is unsafe when it comes to buffer size, could easily overflow
    str.AppendTo<char*>(sinkObj);
  }
};

template <typename SINK_TYPE, typename FORMAT_TYPE, typename ARG1_TYPE>
void Format(SINK_TYPE& sink, FORMAT_TYPE format, const ARG1_TYPE& arg1)
{
  SinkDrain<SINK_TYPE> drain(sink);

  FormatStringPtr<FORMAT_TYPE> ptr(format);
  do
  {
    if (ptr.IsString())
    {
      drain.Drain(ptr);
    } else if (ptr.IsArgument())
    {
      FormatArgument arg = ptr.GetArgument(1);
      switch (arg.Number)
      {
      case -1:
        drain.Drain(ptr);
        break;
      case 0:
        ToString(&drain, arg1, arg);
        break;
      }
    }
  } while (!ptr.HasEnded());
}

template <typename SINK_TYPE, typename FORMAT_TYPE, typename ARG1_TYPE, typename ARG2_TYPE>
void Format(SINK_TYPE& sink, FORMAT_TYPE format, const ARG1_TYPE& arg1, const ARG2_TYPE& arg2)
{
  SinkDrain<SINK_TYPE> drain(sink);

  FormatStringPtr<FORMAT_TYPE> ptr(format);
  do
  {
    if (ptr.IsString())
    {
      drain.Drain(ptr);
    } else if (ptr.IsArgument())
    {
      FormatArgument arg = ptr.GetArgument(2);
      switch (arg.Number)
      {
      case 0:
        ToString(&drain, arg1, arg);
        break;
      case 1:
        ToString(&drain, arg2, arg);
        break;
      }
    }
  } while (ptr.Next());
}

template <typename SINK_TYPE, typename FORMAT_TYPE, typename ARG1_TYPE, typename ARG2_TYPE, typename ARG3_TYPE>
void Format(SINK_TYPE& sink, FORMAT_TYPE format, const ARG1_TYPE& arg1, const ARG2_TYPE& arg2, const ARG3_TYPE& arg3)
{
  SinkDrain<SINK_TYPE> drain(sink);

  FormatStringPtr<FORMAT_TYPE> ptr(format);
  do
  {
    if (ptr.IsString())
    {
      drain.Drain(ptr);
    } else if (ptr.IsArgument())
    {
      FormatArgument arg = ptr.GetArgument(3);
      switch (arg.Number)
      {
      case 0:
        ToString(&drain, arg1, arg);
        break;
      case 1:
        ToString(&drain, arg2, arg);
        break;
      case 2:
        ToString(&drain, arg3, arg);
        break;
      }
    }
  } while (ptr.Next());
}

template <typename SINK_TYPE, typename FORMAT_TYPE, typename ARG1_TYPE, typename ARG2_TYPE, typename ARG3_TYPE,
typename ARG4_TYPE>
void Format(SINK_TYPE& sink, FORMAT_TYPE format, const ARG1_TYPE& arg1, const ARG2_TYPE& arg2, 
            const ARG3_TYPE& arg3, const ARG4_TYPE& arg4)
{
  SinkDrain<SINK_TYPE> drain(sink);

  FormatStringPtr<FORMAT_TYPE> ptr(format);
  do
  {
    if (ptr.IsString())
    {
      drain.Drain(ptr);
    } else if (ptr.IsArgument())
    {
      FormatArgument arg = ptr.GetArgument(4);
      switch (arg.Number)
      {
      case 0:
        ToString(&drain, arg1, arg);
        break;
      case 1:
        ToString(&drain, arg2, arg);
        break;
      case 2:
        ToString(&drain, arg3, arg);
        break;
      case 3:
        ToString(&drain, arg4, arg);
        break;
      }
    }
  } while (ptr.Next());
}

template <typename SINK_TYPE, typename FORMAT_TYPE, typename ARG1_TYPE, typename ARG2_TYPE, typename ARG3_TYPE,
typename ARG4_TYPE, typename ARG5_TYPE>
void Format(SINK_TYPE& sink, FORMAT_TYPE format, const ARG1_TYPE& arg1, const ARG2_TYPE& arg2, 
            const ARG3_TYPE& arg3, const ARG4_TYPE& arg4, const ARG5_TYPE& arg5)
{
  SinkDrain<SINK_TYPE> drain(sink);

  FormatStringPtr<FORMAT_TYPE> ptr(format);
  do
  {
    if (ptr.IsString())
    {
      drain.Drain(ptr);
    } else if (ptr.IsArgument())
    {
      FormatArgument arg = ptr.GetArgument(5);
      switch (arg.Number)
      {
      case 0:
        ToString(&drain, arg1, arg);
        break;
      case 1:
        ToString(&drain, arg2, arg);
        break;
      case 2:
        ToString(&drain, arg3, arg);
        break;
      case 3:
        ToString(&drain, arg4, arg);
        break;
      case 4:
        ToString(&drain, arg5, arg);
        break;
      }
    }
  } while (ptr.Next());
}

template <typename SINK_TYPE, typename FORMAT_TYPE, typename ARG1_TYPE, typename ARG2_TYPE, typename ARG3_TYPE,
typename ARG4_TYPE, typename ARG5_TYPE, typename ARG6_TYPE>
void Format(SINK_TYPE& sink, FORMAT_TYPE format, const ARG1_TYPE& arg1, const ARG2_TYPE& arg2, 
            const ARG3_TYPE& arg3, const ARG4_TYPE& arg4, const ARG5_TYPE& arg5, const ARG6_TYPE& arg6)
{
  SinkDrain<SINK_TYPE> drain(sink);

  FormatStringPtr<FORMAT_TYPE> ptr(format);
  do
  {
    if (ptr.IsString())
    {
      drain.Drain(ptr);
    } else if (ptr.IsArgument())
    {
      FormatArgument arg = ptr.GetArgument(6);
      switch (arg.Number)
      {
      case 0:
        ToString(&drain, arg1, arg);
        break;
      case 1:
        ToString(&drain, arg2, arg);
        break;
      case 2:
        ToString(&drain, arg3, arg);
        break;
      case 3:
        ToString(&drain, arg4, arg);
        break;
      case 4:
        ToString(&drain, arg5, arg);
        break;
      case 5:
        ToString(&drain, arg6, arg);
        break;
      }
    }
  } while (ptr.Next());
}

template <typename SINK_TYPE, typename FORMAT_TYPE, typename ARG1_TYPE, typename ARG2_TYPE, typename ARG3_TYPE,
typename ARG4_TYPE, typename ARG5_TYPE, typename ARG6_TYPE, typename ARG7_TYPE>
void Format(SINK_TYPE& sink, FORMAT_TYPE format, const ARG1_TYPE& arg1, const ARG2_TYPE& arg2, 
            const ARG3_TYPE& arg3, const ARG4_TYPE& arg4, const ARG5_TYPE& arg5, const ARG6_TYPE& arg6,
            const ARG7_TYPE& arg7)
{
  SinkDrain<SINK_TYPE> drain(sink);

  FormatStringPtr<FORMAT_TYPE> ptr(format);
  do
  {
    if (ptr.IsString())
    {
      drain.Drain(ptr);
    } else if (ptr.IsArgument())
    {
      FormatArgument arg = ptr.GetArgument(7);
      switch (arg.Number)
      {
      case 0:
        ToString(&drain, arg1, arg);
        break;
      case 1:
        ToString(&drain, arg2, arg);
        break;
      case 2:
        ToString(&drain, arg3, arg);
        break;
      case 3:
        ToString(&drain, arg4, arg);
        break;
      case 4:
        ToString(&drain, arg5, arg);
        break;
      case 5:
        ToString(&drain, arg6, arg);
        break;
      case 6:
        ToString(&drain, arg7, arg);
        break;
      }
    }
  } while (ptr.Next());
}

template <typename SINK_TYPE, typename FORMAT_TYPE, typename ARG1_TYPE, typename ARG2_TYPE, typename ARG3_TYPE,
typename ARG4_TYPE, typename ARG5_TYPE, typename ARG6_TYPE, typename ARG7_TYPE, typename ARG8_TYPE>
void Format(SINK_TYPE& sink, FORMAT_TYPE format, const ARG1_TYPE& arg1, const ARG2_TYPE& arg2, 
            const ARG3_TYPE& arg3, const ARG4_TYPE& arg4, const ARG5_TYPE& arg5, const ARG6_TYPE& arg6,
            const ARG7_TYPE& arg7, const ARG8_TYPE& arg8)
{
  SinkDrain<SINK_TYPE> drain(sink);

  FormatStringPtr<FORMAT_TYPE> ptr(format);
  do
  {
    if (ptr.IsString())
    {
      drain.Drain(ptr);
    } else if (ptr.IsArgument())
    {
      FormatArgument arg = ptr.GetArgument(8);
      switch (arg.Number)
      {
      case 0:
        ToString(&drain, arg1, arg);
        break;
      case 1:
        ToString(&drain, arg2, arg);
        break;
      case 2:
        ToString(&drain, arg3, arg);
        break;
      case 3:
        ToString(&drain, arg4, arg);
        break;
      case 4:
        ToString(&drain, arg5, arg);
        break;
      case 5:
        ToString(&drain, arg6, arg);
        break;
      case 6:
        ToString(&drain, arg7, arg);
        break;
      case 7:
        ToString(&drain, arg8, arg);
        break;
      }
    }
  } while (ptr.Next());
}

template <typename SINK_TYPE, typename FORMAT_TYPE, typename ARG1_TYPE, typename ARG2_TYPE, typename ARG3_TYPE,
typename ARG4_TYPE, typename ARG5_TYPE, typename ARG6_TYPE, typename ARG7_TYPE, typename ARG8_TYPE,
typename ARG9_TYPE>
void Format(SINK_TYPE& sink, FORMAT_TYPE format, const ARG1_TYPE& arg1, const ARG2_TYPE& arg2, 
            const ARG3_TYPE& arg3, const ARG4_TYPE& arg4, const ARG5_TYPE& arg5, const ARG6_TYPE& arg6,
            const ARG7_TYPE& arg7, const ARG8_TYPE& arg8, const ARG9_TYPE& arg9)
{
  SinkDrain<SINK_TYPE> drain(sink);

  FormatStringPtr<FORMAT_TYPE> ptr(format);
  do
  {
    if (ptr.IsString())
    {
      drain.Drain(ptr);
    } else if (ptr.IsArgument())
    {
      FormatArgument arg = ptr.GetArgument(9);
      switch (arg.Number)
      {
      case 0:
        ToString(&drain, arg1, arg);
        break;
      case 1:
        ToString(&drain, arg2, arg);
        break;
      case 2:
        ToString(&drain, arg3, arg);
        break;
      case 3:
        ToString(&drain, arg4, arg);
        break;
      case 4:
        ToString(&drain, arg5, arg);
        break;
      case 5:
        ToString(&drain, arg6, arg);
        break;
      case 6:
        ToString(&drain, arg7, arg);
        break;
      case 7:
        ToString(&drain, arg8, arg);
        break;
      case 8:
        ToString(&drain, arg9, arg);
        break;
      }
    }
  } while (ptr.Next());
}

#endif